package cn.zch.soft.util.file.zip;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
 * ZipUtils
 *
 * @author ZENG.XIAO.YAN
 * @version v1.0
 * @date 2017年11月19日 下午7:16:08
 */
@Slf4j
public class ZipUtils {
  private static final int BUFFER_SIZE = 2 * 1024;
  private ZipUtils() {
  }
  /**
   * 压缩成ZIP 方法1
   *
   * @param srcDir           压缩文件夹路径
   * @param out              压缩文件输出流
   * @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构;
   *                         <p>
   *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
   * @throws RuntimeException 压缩失败会抛出运行时异常
   */
  public static void toZip(String srcDir, OutputStream out, boolean keepDirStructure) {
    long start = System.currentTimeMillis();
    try (ZipOutputStream zos = new ZipOutputStream(out)) {
      File sourceFile = new File(srcDir);
      compress(sourceFile, zos, sourceFile.getName(), keepDirStructure);
      long end = System.currentTimeMillis();
      log.info("压缩完成，耗时：" + (end - start) + " ms");
    }
    catch (Exception e) {
      log.error(e.getMessage(), e);
    }
  }
  /**
   * 压缩成ZIP 方法2
   *
   * @param srcFiles 需要压缩的文件列表
   * @param out      压缩文件输出流
   * @throws RuntimeException 压缩失败会抛出运行时异常
   */
  public static void toZip(List<File> srcFiles, OutputStream out) {
    long start = System.currentTimeMillis();
    try (ZipOutputStream zos = new ZipOutputStream(out)) {
      for (File srcFile : srcFiles) {
        byte[] buf = new byte[BUFFER_SIZE];
        zos.putNextEntry(new ZipEntry(srcFile.getName()));
        int len;
        try (FileInputStream in = new FileInputStream(srcFile)) {
          while ((len = in.read(buf)) != -1) {
            zos.write(buf, 0, len);
          }
          zos.closeEntry();
        }
      }
      long end = System.currentTimeMillis();
      log.info("压缩完成，耗时：" + (end - start) + " ms");
    }
    catch (Exception e) {
      log.error(e.getMessage(), e);
    }

  }
  /**
   * 递归压缩方法
   *
   * @param sourceFile       源文件
   * @param zos              zip输出流
   * @param name             压缩后的名称
   * @param keepDirStructure 是否保留原来的目录结构,true:保留目录结构;
   *                         <p>
   *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
   */
  private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean keepDirStructure) {
    try {
      if (sourceFile.isFile()) {
        compressFile(sourceFile, zos, name);
      }
      else {
        compressFiles(sourceFile, zos, name, keepDirStructure);
      }
    }
    catch (Exception e) {
      log.error(e.getMessage(), e);
    }
  }
  private static void compressFile(File sourceFile, ZipOutputStream zos, String name) {
    try {
      byte[] buf = new byte[BUFFER_SIZE];
      // 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
      zos.putNextEntry(new ZipEntry(name));
      // copy文件到zip输出流中
      int len;
      try (FileInputStream in = new FileInputStream(sourceFile)) {
        while ((len = in.read(buf)) != -1) {
          zos.write(buf, 0, len);
        }
        // Complete the entry
        zos.closeEntry();
      }
    }
    catch (Exception e) {
      log.error(e.getMessage(), e);
    }
  }
  private static void compressFiles(File sourceFile, ZipOutputStream zos, String name, Boolean keepDirStructure) {
    if (null == keepDirStructure) {
      keepDirStructure = true;
    }
    try {
      File[] listFiles = sourceFile.listFiles();
      if (listFiles == null || listFiles.length == 0) {
        // 需要保留原来的文件结构时,需要对空文件夹进行处理
        if (keepDirStructure) {
          // 空文件夹的处理
          zos.putNextEntry(new ZipEntry(name + "/"));
          // 没有文件，不需要文件的copy
          zos.closeEntry();
        }
      }
      else {
        for (File file : listFiles) {
          // 判断是否需要保留原来的文件结构
          String fileName;
          if (keepDirStructure) {
            // file.getName()前面需要带上父文件夹的名字加一斜杠,不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
            // compress(file, zos, name + "/" + file.getName(), true)
            // fileName = name + "/" + file.getName()
            fileName = name + File.separator + file.getName();
          }
          else {
            fileName = file.getName();
          }
          compress(file, zos, fileName, keepDirStructure);
        }
      }
    }
    catch (Exception e) {
      log.error(e.getMessage(), e);
    }
  }
}
